using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Antlr.StringTemplate;
using Arp.Common.Assertions;
using Arp.Generator.Names;
using Arp.Generator.Preprocessing;
using Arp.Generator.Preprocessing.Impl;
using log4net;

namespace Arp.Generator.Generating
{
    public class TreeElements
    {
        private static readonly ILog log = LogManager.GetLogger(MethodInfo.GetCurrentMethod().DeclaringType);


        #region Templates

        #region BASE

        public const string BASE =
            @"
group common;
ifcName(name) ::= <<I<name>
>>
implName(name) ::= <<
<name>Impl
>>
compositeElementType(name) ::= <<
<name>Impl_ELEMENT_TYPE
>>
compositeElementTypeInstance(name) ::= <<
ElementType.<name>Impl_ELEMENT
>>
compositeElementTypeInstanceName(name) ::= <<
<name>Impl_ELEMENT
>>
roleRef(e) ::= <<
<e.name>_ROLE_ID
>>";

        #endregion

        // TODO create reusable template for footer/headers

        #region INTERFACE_DECLARATION

        public const string INTERFACE_DECLARATION =
            @"
/*
This file was generated by Arp.Generator
*/
#region Imports
using JetBrains.ReSharper.Psi.Xml.Tree;
using JetBrains.ReSharper.Psi.Tree;
using System.Collections.Generic;
$usings:{u |using $u$;
}$
#endregion

namespace $namespace$
{
    public partial interface $ifcName(name=name )$ : $baseTreeIfcElement$ 
    {
$if(noemptybody)$
        #region Attributes

$attributes:{a |
        $a.type$ $a.name$ {get;}
}$
$end$
        #endregion

        #region Elements

$elements:{e |
$if(!e.isCollection)$
        $ifcName(name=e.type)$ $e.name$ {get;}
$else$
        ICollection<$ifcName(name=e.type)$> $e.name$ {get;}
$endif$
}$
        #endregion

$additional$ 

$endif$
    }
}
";

        #endregion

        #region IMPL_DECLARATION

        public const string IMPL_DECLARATION =
            @"
/*
This file was generated by Arp.Generator
*/
#region Imports
using JetBrains.ReSharper.Psi.Xml.Tree;
using JetBrains.ReSharper.Psi.Xml.Impl.Tree;
using System.Collections.Generic;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
$usings:{u |using $u$;
}$
#endregion

namespace $namespace$.Impl
{
    public partial class $implName(name=name )$ : XmlTag,  $ifcName(name=name )$
    {
$if(noemptybody)$

        #region Role ids
$elements:{e |
        private const int $roleRef(e)$ = $e.role$;
}$
        #endregion

        #region Constructor 

        public $implName(name=name)$() : base($compositeElementTypeInstance(name)$) {}

        #endregion

        #region Attributes

$attributes:{a |
        public $a.type$ $a.name$ 
        {
            get
            {
                return base.GetAttribute(""$a.xmlName$"");
            }
        }
}$
$end$
        #endregion

        #region Elements

$elements:{e |
$if(!e.isCollection)$
        public $ifcName(name=e.type)$ $e.name$
        {
            get
            {
                return ($ifcName(name=e.type)$)FindChildByRole($roleRef(e)$);
            }
        }
$else$
        public ICollection<$ifcName(name=e.type)$> $e.name$
        { 
            get
            { 
                return base.FindListOfChildrenByRole<$ifcName(name=e.type)$>($roleRef(e)$);
            } 
        }
$endif$

}$

        #endregion

$additional$ 

        public override short GetChildRole(TreeElement child)
        {
        $elements:{e |
        if (child is $ifcName(name=e.type)$ && ((XmlTag)child).TagName == ""$e.xmlName$"")
            return $roleRef(e)$;
        else 
        }$
        return base.GetChildRole(child);
        }
$endif$

    }
}
";

        #endregion

        #region ELEMENT_TYPES_DECLARATION

        public const string ELEMENT_TYPES_DECLARATION =
            @"
/*
This file was generated by Arp.Generator
*/

using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;

namespace $namespace$.Impl
{
$types:{t |
    public class $compositeElementType(t.name)$ : $baseElementType$
    {
        public $compositeElementType(t.name)$(string s)
            : base(s)
        {
        }

        public override CompositeElement Create()
        {
            return new $implName(t.name)$();
        }
    }
}$
}
";

        #endregion

        #region ELEMENT_TYPE_INSTANCES_DECLARATION

        public const string ELEMENT_TYPE_INSTANCES_DECLARATION =
            @"
/*
This file was generated by Arp.Generator
*/
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;

namespace $namespace$.Impl
{
  public partial class ElementType
  {
$if(noemptybody)$
$types:{t|
        public static readonly CompositeNodeType $compositeElementTypeInstanceName(t.name)$ = 
            new $compositeElementType(t.name)$(""$compositeElementTypeInstance(t.name)$"");        
}$  
$endif$    
  }
}
";

        #endregion

//                if (name == ""$nn.Name$"")
//                {
//                    return new $implName(elementBaseNames.(nn.Name))$();
        //                }

        #region ELEMENTS_FACTORY

        public const string ELEMENTS_FACTORY =
            @"
using $namespace$.Impl;
using JetBrains.ReSharper.Psi.Xml.Tree;

namespace $namespace$.Parsing
{
    public partial class $elementsFactoryName$
    {
        protected IXmlTag CreateTagGenerated(IXmlTagHeaderNode header, IXmlTagContainer parentTag)
        {

            string name = header.Name.GetText();
$elements:{e|
            if(parentTag is $ifcName(elementBaseNames.(e.Name))$)
            {
$e.TypeInfo.NestedElementsInfo:{n|
$if(!n.IsCollection)$
                if (name == ""$n.Element.Name$"")
                {
                    return new $implName(elementBaseNames.(n.Element.Name))$();
                }
$else$
$n.Elements:{nn|
                if (name == ""$nn.Name$"")
                {
                    return new $implName(elementBaseNames.(nn.Name))$();
                }
}$
$endif$

                else
}$                
                    return null;
            }
            else
}$
                return null;
            }        
    }
}";

        #endregion

        #region PROPERTY_DECLARATION

        public const string PROPERTY_DECLARATION =
            @"
        $type$ $name$
        {
            get;
        }
}";

        #endregion


        #endregion

        private IFilesWriter fileWriter;
        private INameConverter nameConverter;
        private IPluralProvider pluralProvider;
        private string baseNamespace = "Arp.NH.Psi.Tree";
        private string baseElementType = "NHConpositeElementType";
        private string elementsFactoryName = "NHElementsFactory";
        private string baseTreeIfcElement = "INHElement";
        
        private readonly StringTemplateGroup group;
        private readonly StringTemplateGroup templateGroup;
        

        #region Constructors

        public TreeElements()
        {
            group = new StringTemplateGroup(new StringReader(BASE));
            templateGroup = new StringTemplateGroup("interface");
                templateGroup.SuperGroup = group;
            templateGroup.DefineTemplate("interface", INTERFACE_DECLARATION);
            templateGroup.DefineTemplate("impl", IMPL_DECLARATION);
            templateGroup.DefineTemplate("elementTypes", ELEMENT_TYPES_DECLARATION);
            templateGroup.DefineTemplate("elementTypeInstances", ELEMENT_TYPE_INSTANCES_DECLARATION);
            templateGroup.DefineTemplate("elementsFactory", ELEMENTS_FACTORY);

        }

        #endregion

        #region Properties

        public IFilesWriter FileWriter
        {
            get { return fileWriter; }
            set { fileWriter = value; }
        }


        public INameConverter NameConverter
        {
            get { return nameConverter; }
            set { nameConverter = value; }
        }


        public IPluralProvider PluralProvider
        {
            get { return pluralProvider; }
            set { pluralProvider = value; }
        }


        public string BaseNamespace
        {
            get { return baseNamespace; }
            set { baseNamespace = value; }
        }

        public string BaseElementType
        {
            get { return baseElementType; }
            set { baseElementType = value; }
        }

        public string ElementsFactoryName
        {
            get { return elementsFactoryName; }
            set { elementsFactoryName = value; }
        }


        public string BaseTreeIfcElement
        {
            get { return baseTreeIfcElement; }
            set { baseTreeIfcElement = value; }
        }

        #endregion

        #region Public Methods

        public void GenerateElementInterface(ITypeInfo typeGenerationInfo)
        {
            Assert.CheckNotNull(fileWriter);
            Assert.CheckNotNull(nameConverter);
            Assert.CheckNotNull(pluralProvider);

            #region Logging

            if (log.IsWarnEnabled)
            {
                log.Info("Generate tree interface for " + typeGenerationInfo.BaseName);
            }

            #endregion

            StringTemplate stringTemplate = templateGroup.GetInstanceOf("interface");

            stringTemplate.SetAttribute("baseTreeIfcElement", BaseTreeIfcElement);

            //StringTemplate stringTemplate = new StringTemplate(INTERFACE_DECLARATION);

            string baseName = nameConverter.ConvertTypeName(typeGenerationInfo.BaseName);

            PrepareTreeElementTemplate(typeGenerationInfo, stringTemplate, baseName);
            string ret = stringTemplate.ToString();
            string ifcName = GetIfcName(baseName);
            fileWriter.Write(ifcName + ".generated.cs", ret);

            string onceGenerateFile = ifcName + ".cs";
            if (!fileWriter.Exists(onceGenerateFile))
            {
                stringTemplate.RemoveAttribute("noemptybody");
                fileWriter.Write(onceGenerateFile, stringTemplate.ToString());
            }
        }

        public void GenerateElementImpl(ITypeInfo typeGenerationInfo)
        {
            Assert.CheckNotNull(fileWriter);
            Assert.CheckNotNull(nameConverter);
            Assert.CheckNotNull(pluralProvider);

            #region Logging

            if (log.IsWarnEnabled)
            {
                log.Info("Generate tree implementation for " + typeGenerationInfo.BaseName);
            }

            #endregion

            StringTemplate stringTemplate = templateGroup.GetInstanceOf("impl");

            string baseName = nameConverter.ConvertTypeName(typeGenerationInfo.BaseName);

            PrepareTreeElementTemplate(typeGenerationInfo, stringTemplate, baseName);

            string ret = stringTemplate.ToString();
            string implName = GetImplName(baseName);
            fileWriter.Write("Impl\\" + implName + ".generated.cs", ret);

            string onceGenerateFile = "Impl\\" + implName + ".cs";
            if (!fileWriter.Exists(onceGenerateFile))
            {
                stringTemplate.RemoveAttribute("noemptybody");
                fileWriter.Write(onceGenerateFile, stringTemplate.ToString());
            }

        }

        public void GenerateCompositeElementTypes(ICollection<ITypeInfo> typeGenerationInfos)
        {
            #region Logging

            if (log.IsWarnEnabled)
            {
                log.Info("Generate element types ");
            }

            #endregion            
            
            StringTemplate stringTemplate = templateGroup.GetInstanceOf("elementTypes");

            PrepareElementTypes(stringTemplate, typeGenerationInfos);
            stringTemplate.SetAttribute("baseElementType", this.BaseElementType);

            string baseFileName = "CompositeElementTypes";
            fileWriter.Write("Impl\\" + baseFileName + ".generated.cs", stringTemplate.ToString());

        }

        public void GenerateCompositeElementTypeInstrances(ICollection<ITypeInfo> typeGenerationInfos)
        {
            #region Logging

            if (log.IsWarnEnabled)
            {
                log.Info("Generate element types static instances");
            }

            #endregion                        
            
            StringTemplate stringTemplate = templateGroup.GetInstanceOf("elementTypeInstances");

            PrepareElementTypes(stringTemplate, typeGenerationInfos);
            stringTemplate.SetAttribute("noemptybody", true);
            string baseFileName = "ElementType";
            fileWriter.Write("Impl\\" + baseFileName + ".generated.cs", stringTemplate.ToString());

            string onceGenerateFile = "Impl\\" + baseFileName + ".cs";
            if (!fileWriter.Exists(onceGenerateFile))
            {
                stringTemplate.RemoveAttribute("noemptybody");
                fileWriter.Write(onceGenerateFile, stringTemplate.ToString());
            }
        }

        public void GenerateElementsFactory(ICollection<IElementInfo> elementInfos)
        {
            #region Logging

            if (log.IsWarnEnabled)
            {
                log.Info("Generate elements factory");
            }

            #endregion                                    

            
            StringTemplate stringTemplate = this.templateGroup.GetInstanceOf("elementsFactory");
            stringTemplate.SetAttribute("namespace", BaseNamespace);
            stringTemplate.SetAttribute("elementsFactoryName", ElementsFactoryName);

            Dictionary<string,string> elementBaseNames = new Dictionary<string, string>();
            foreach (IElementInfo info in elementInfos)
            {
                elementBaseNames[info.Name] = this.nameConverter.ConvertTypeName(info.TypeInfo.BaseName);
            }
            
            stringTemplate.SetAttribute("elementBaseNames", elementBaseNames);
            stringTemplate.SetAttribute("elements", elementInfos);

            fileWriter.Write("Parsing\\" + ElementsFactoryName + ".generated.cs", stringTemplate.ToString());
        }

        #endregion

        #region Private Methods

        private void PrepareElementTypes(StringTemplate stringTemplate, ICollection<ITypeInfo> typeGenerationInfos)
        {
            stringTemplate.SetAttribute("namespace", BaseNamespace);

            foreach (ITypeInfo info in typeGenerationInfos)
            {
                stringTemplate.SetAttribute("types.{name,f}", nameConverter.ConvertTypeName(info.BaseName),"f  ");
            }
        }

        private void PrepareTreeElementTemplate(ITypeInfo typeGenerationInfo, StringTemplate stringTemplate, string baseName)
        {
            stringTemplate.SetAttribute("namespace", BaseNamespace);
            stringTemplate.SetAttribute("name", baseName);

            stringTemplate.SetAttribute("noemptybody", true);

            foreach (IAttributeInfo info in typeGenerationInfo.AttributesInfo)
            {
                stringTemplate.SetAttribute("attributes.{type,name,xmlName}", "IXmlAttribute", nameConverter.ConvertAttributeName(info.Name), info.Name);
            }

            int childRoleCount = 20;
            foreach (INestedElementInfo nestedElementInfo in typeGenerationInfo.NestedElementsInfo)
            {
                if (!nestedElementInfo.IsCollection)
                {
                    stringTemplate.SetAttribute("elements.{isCollection,type,name,role,xmlName}", false,nameConverter.ConvertTypeName(nestedElementInfo.Element.TypeInfo.BaseName),
                                                nameConverter.ConvertElementName(nestedElementInfo.Element.Name), childRoleCount++, nestedElementInfo.Element.Name);
                }
                else
                {
                    foreach (IElementInfo collectionElementInfo in nestedElementInfo.Elements)
                    {
                        string name = nameConverter.ConvertElementName(collectionElementInfo.Name);
                        name = pluralProvider.Plural(name);
                        stringTemplate.SetAttribute("elements.{isCollection,type,name,role,xmlName}", true, nameConverter.ConvertTypeName(collectionElementInfo.TypeInfo.BaseName),
                                                    name, childRoleCount++, collectionElementInfo.Name);
                    }
                }
            }

            stringTemplate.SetAttribute("attitional", ";;");
        }

        private string GetIfcName(string baseName)
        {
            StringTemplate ifcNameInstance = templateGroup.GetInstanceOf("ifcName");
            ifcNameInstance.SetAttribute("name", baseName);
            return ifcNameInstance.ToString();
        }

        private string GetImplName(string baseName)
        {
            StringTemplate ifcNameInstance = templateGroup.GetInstanceOf("implName");
            ifcNameInstance.SetAttribute("name", baseName);
            return ifcNameInstance.ToString();
        }

        #endregion

    }
}